﻿using System;
using ASquared;
using ASquared.SymbolicMath;

namespace ASquared.ModelOptimization
{
    /// <summary>Defines the various line search methods.</summary>
    public enum LineSearchMethod { Golden }

    #region Interface and an implementation

    /// <summary>Defines an object that a line search can be performed on.</summary>
    public interface ILineSearchSolvable
    {
        /// <summary>The single decision variable</summary>
        Double x { get; set; }
        /// <summary>The function with respect to the decision variable</summary>
        Symbol fxn { get; set; }
    }

    /// <summary>A general objective function defined by a Symbol object.</summary>
    public class LineSearchObjective : ILineSearchSolvable
    {
        private Symbol _fxn;
        private Double _x;

        public Symbol fxn { get { return _fxn; } set { _fxn = value; } }
        public Double x { get { return _x; } set { _x = value; } }

        /// <summary>Constructs a quadratic function of the form f(x) = a + b * x + c * x ^ 2</summary>
        public LineSearchObjective(Symbol s)
        {
            _fxn = s;
        }

        public static implicit operator LineSearchObjective(Symbol s)
        {
            return new LineSearchObjective(s);
        }
        public static implicit operator Symbol(LineSearchObjective l)
        {
            return l._fxn;
        }

        public override string ToString()
        {
            return _fxn.ToString();
        }
    }

    #endregion

    #region Line search solver

    /// <summary>Performs a minimization line search over an object that implements the ILineSearchSolvable interface.</summary>
    public class LineSearchSolver
    {
        private Int32 _maxiters = 1000;
        private Double _tolerance = Math.Sqrt(0.001);
        private Bounds _bnds = new Bounds();
        private ILineSearchSolvable _obj;
        private LineSearchMethod _method;
        private Int32 _iter = 0;

        // Properties
        /// <summary>Gets and sets the tolerance of the solution.</summary>
        public Double Tolerance { get { return Math.Pow(_tolerance, 2); } set { _tolerance = Math.Sqrt(value); _bnds.Tolerance = value; } }
        /// <summary>Gets and sets the maximum number of iterations.</summary>
        public Int32 MaxIterations { get { return _maxiters; } set { _maxiters = value; } }
        /// <summary>Gets the current iteration.</summary>
        public Int32 Iteration { get { return _iter; } }
        /// <summary>Gets and sets the bounds on the solution.</summary>
        public Bounds Bounds { get { return _bnds; } set { _bnds = value; } }
        /// <summary>Gets and sets the method of solution.</summary>
        public LineSearchMethod Method { get { return _method; } set { _method = value; } }

        // Constructors
        public LineSearchSolver(ILineSearchSolvable obj, LineSearchMethod method)
        {
            _obj = obj;
            _method = method;
        }

        // Methods 
        /// <summary>Solves the single variable optimization via the specified line search method.</summary>
        /// <param name="x_init">The initial feasible solution for the optimization.</param>
        /// <param name="step_init">The initial step.</param>
        /// <returns>Returns the optimized function value of the line search.</returns>
        public Double Solve(Object[] parameters)
        {
            switch (_method)
            {
                case LineSearchMethod.Golden:
                    if (parameters == null)
                    {
                        if (Double.IsInfinity(_bnds.Lower) || Double.IsInfinity(_bnds.Upper))
                            throw new ArgumentOutOfRangeException("Bounds must be specified to run the golden search method.");
                        parameters = new Object[] { _bnds.Lower, _bnds.Upper };
                    }
                    return this.GoldenNonRecursive(Convert.ToDouble(parameters[0]), Convert.ToDouble(parameters[1]));
                default:
                    throw new NotImplementedException(_method.ToString() + " is not implemented in the line-search solution.");
            }
        }

        // Golden search 
        /// <summary>Performs a line search using the golden section search method.</summary>
        /// <param name="a">Current lower bound</param>
        /// <param name="b">Center point</param>
        /// <param name="c">Current upper bound</param>
        /// <returns>Returns the optimal function value.</returns>
        private Double GoldenNonRecursive(Double a, Double c)
        {
            // Golden search variables
            Double phi = (1 + Math.Sqrt(5)) / 2;
            Double resphi = 2 - phi;
            Double b = (a + c) / 2.0;
            Double fb = _obj.fxn.Eval(b);
            Double fx;
            _iter = 0;

            // Iterations
            double x = b;
            while (_iter++ < _maxiters)
            {
                if (c - b > b - a)
                    x = b + resphi * (c - b);
                else
                    x = b - resphi * (b - a);
                if (Math.Abs(c - a) < _tolerance * (Math.Abs(b) + Math.Abs(x)))
                {
                    x = (c + a) / 2;
                    break;
                }
                _obj.x = x;
                if ((fx = _obj.fxn.Eval(x)) < fb)
                {
                    if (c - b > b - a)
                        a = b;
                    else
                        c = b;
                    b = x;
                    fb = fx;
                    continue;
                }
                else
                {
                    if (c - b > b - a)
                        c = x;
                    else
                        a = x;
                    continue;
                }
            }
            _obj.x = x;
            return _obj.fxn.Eval(_obj.x);
        }
    }

    #endregion
}
